Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
591c7ae
gnhf #2: Simplified the SDK signing-provider contract by reusing the …
ggonzalez94 Apr 27, 2026
7e8a3cb
gnhf #3: Simplified built-in app grant matching by replacing duplicat…
ggonzalez94 Apr 27, 2026
5a43134
gnhf #4: Simplified the core scheduling fallback by replacing its rem…
ggonzalez94 Apr 27, 2026
cd049a5
gnhf #5: Simplified queued TAP command typing by preserving the comma…
ggonzalez94 Apr 27, 2026
a769a4b
gnhf #6: Replaced remaining hand-built grant-set fixtures in focused …
ggonzalez94 Apr 27, 2026
4e48c82
gnhf #7: Centralized the remaining hand-built TAP grant-set fixtures …
ggonzalez94 Apr 27, 2026
e65792b
gnhf #8: Centralized the last TypeScript hard-coded TAP grant-set ver…
ggonzalez94 Apr 27, 2026
20aea15
gnhf #9: Centralized duplicated built-in app action ID generation in …
ggonzalez94 Apr 27, 2026
4fd7c79
gnhf #10: Centralized repeated core JSON atomic-write logic behind a …
ggonzalez94 Apr 27, 2026
8d6d4c2
gnhf #11: Centralized two remaining core JSON persistence save paths …
ggonzalez94 Apr 27, 2026
741ddd6
gnhf #12: Centralized CLI atomic JSON/YAML serialization helpers and …
ggonzalez94 Apr 27, 2026
5ce8d18
gnhf #13: Centralized the remaining CLI YAML config writes on the sha…
ggonzalez94 Apr 27, 2026
99665f6
gnhf #14: Centralized duplicated Hermes JSON file loading logic while…
ggonzalez94 Apr 27, 2026
2190aed
gnhf #15: Centralized the register IPFS cache write on the shared CLI…
ggonzalez94 Apr 27, 2026
0ad047e
gnhf #16: Centralized core JSON file loading for app manifest, app st…
ggonzalez94 Apr 27, 2026
6e7a777
gnhf #17: Centralized the remaining JSON read paths in the file-backe…
ggonzalez94 Apr 27, 2026
8a82d64
gnhf #18: Centralized two remaining core JSON load paths on the share…
ggonzalez94 Apr 27, 2026
b7d4fce
gnhf #19: Centralized CLI JSON read-with-default behavior in the shar…
ggonzalez94 Apr 27, 2026
69bf42b
gnhf #20: Centralized remaining asynchronous CLI config YAML reads be…
ggonzalez94 Apr 27, 2026
41f6b18
gnhf #21: Centralized the remaining calendar config YAML parsing behi…
ggonzalez94 Apr 27, 2026
383d370
gnhf #22: Centralized the wallet config synchronous YAML read path on…
ggonzalez94 Apr 27, 2026
86df5e0
gnhf #23: Centralized the final `tap status` sync YAML config read on…
ggonzalez94 Apr 27, 2026
ebed673
gnhf #24: Centralized the remaining core strict JSON file reads used …
ggonzalez94 Apr 27, 2026
8dcec0b
gnhf #25: Centralized transport owner lock JSON loading on the shared…
ggonzalez94 Apr 27, 2026
0cc01b1
gnhf #26: Centralized the CLI's remaining strict JSON file read path …
ggonzalez94 Apr 27, 2026
e1c667c
gnhf #27: Centralized duplicated tapd transfer-route primitive valida…
ggonzalez94 Apr 27, 2026
118a07f
gnhf #28: Centralized tapd HTTP transfer-field validation so executio…
ggonzalez94 Apr 27, 2026
0e2e47a
gnhf #29: Centralized tapd finite/positive number validation for invi…
ggonzalez94 Apr 27, 2026
2789b12
gnhf #30: Centralized repeated tapd peer-field route validation behin…
ggonzalez94 Apr 27, 2026
27d33b6
gnhf #31: Centralized tapd route boolean validation behind shared HTT…
ggonzalez94 Apr 27, 2026
dd4ff27
gnhf #32: Centralized repeated tapd optional-string route validation …
ggonzalez94 Apr 27, 2026
0d2e76a
gnhf #33: Centralized a few remaining tapd route validation primitive…
ggonzalez94 Apr 27, 2026
9499ce5
gnhf #34: Centralized tapd pending-route optional string extraction o…
ggonzalez94 Apr 27, 2026
b4ca713
gnhf #35: Centralized Hermes config trim-and-nonempty string normaliz…
ggonzalez94 Apr 27, 2026
beeace4
gnhf #36: Centralized Hermes config object-record validation without …
ggonzalez94 Apr 27, 2026
a3506cd
gnhf #37: Simplified OpenClaw plugin config parsing by centralizing o…
ggonzalez94 Apr 27, 2026
2ea0640
gnhf #38: Centralized OpenClaw gateway tool trim-and-nonempty string …
ggonzalez94 Apr 27, 2026
c42416e
gnhf #39: Centralized tapd meeting title non-blank validation behind …
ggonzalez94 Apr 27, 2026
545f8a5
gnhf #40: Centralized OpenClaw plugin trim-and-nonempty string normal…
ggonzalez94 Apr 27, 2026
5a9a932
gnhf #41: Simplified Hermes config numeric validation by reusing one …
ggonzalez94 Apr 27, 2026
b17aa4f
gnhf #42: Centralized OpenClaw tapd token-file loading so the client …
ggonzalez94 Apr 27, 2026
19fe3e6
gnhf #43: Centralized repeated blank-string checks in core scheduling…
ggonzalez94 Apr 27, 2026
2b9fa8c
gnhf #44: Centralized repeated non-empty string parsing in core sched…
ggonzalez94 Apr 27, 2026
03a46ab
gnhf #45: Centralized transfer app payload validation on the core tra…
ggonzalez94 Apr 27, 2026
646620a
gnhf #46: Centralized scheduling app payload validation on a shared c…
ggonzalez94 Apr 27, 2026
8091ade
gnhf #47: Centralized CLI tapd token discovery on the tapd package's …
ggonzalez94 Apr 27, 2026
28185cc
gnhf #48: Centralized OpenClaw tapd token-path derivation on the tapd…
ggonzalez94 Apr 27, 2026
3e10d91
gnhf #49: Centralized core non-empty string parsing helpers and reuse…
ggonzalez94 Apr 27, 2026
035dba3
gnhf #50: Centralized the runtime scheduling fallback parser on the s…
ggonzalez94 Apr 27, 2026
569215d
gnhf #51: Centralized duplicated stored action-request recovery wrapp…
ggonzalez94 Apr 27, 2026
a3e1a1a
gnhf #52: Centralized core action-result envelope parsing so transfer…
ggonzalez94 Apr 27, 2026
664d3c8
gnhf #53: Centralized action/request envelope extraction so transfer,…
ggonzalez94 Apr 27, 2026
8c089f3
gnhf #54: Simplified scheduling payload alias-field parsing by replac…
ggonzalez94 Apr 27, 2026
a33699e
gnhf #55: Centralized core object-shape validation helpers and reused…
ggonzalez94 Apr 27, 2026
7517772
gnhf #56: Reused the shared core record-shape validation helper in th…
ggonzalez94 Apr 27, 2026
c53e115
gnhf #57: Reused core validation primitives in tapd HTTP validation t…
ggonzalez94 Apr 27, 2026
8d62dbe
gnhf #58: Simplified CLI output field-selection internals by reusing …
ggonzalez94 Apr 27, 2026
4855c90
gnhf #59: Centralized remaining strict mapping-shape checks in CLI st…
ggonzalez94 Apr 27, 2026
3d24000
gnhf #60: Reused the shared core record validator in Hermes config pa…
ggonzalez94 Apr 27, 2026
2557b55
gnhf #61: Simplified registration-file validation by centralizing XMT…
ggonzalez94 Apr 27, 2026
3129330
gnhf #62: Centralized several remaining core non-null object guards o…
ggonzalez94 Apr 27, 2026
86e7277
gnhf #63: Centralized calendar config object-shape checks on the shar…
ggonzalez94 Apr 27, 2026
102e688
gnhf #64: Simplified tapd grants route validation by reusing the shar…
ggonzalez94 Apr 27, 2026
f75e937
gnhf #65: Centralized TAP app shape validation so SDK app installatio…
ggonzalez94 Apr 27, 2026
46200f7
gnhf #66: Centralized remaining core protocol/conversation object-sha…
ggonzalez94 Apr 27, 2026
1f21644
gnhf #67: Reused the shared core non-empty-string validator in remain…
ggonzalez94 Apr 27, 2026
f2022bb
gnhf #68: Simplified core grant normalization by reusing shared valid…
ggonzalez94 Apr 27, 2026
a914a74
gnhf #69: Centralized several remaining core runtime parser object an…
ggonzalez94 Apr 27, 2026
af0b60e
gnhf #70: Simplified runtime conversation helpers by reusing the shar…
ggonzalez94 Apr 27, 2026
0c0bfca
gnhf #71: Simplified XMTP sync-state string validation by reusing the…
ggonzalez94 Apr 27, 2026
321c376
gnhf #72: Centralized a few remaining core non-empty string checks on…
ggonzalez94 Apr 27, 2026
ec4e581
gnhf #73: Centralized the last behavior-equivalent non-empty string c…
ggonzalez94 Apr 27, 2026
7558aa1
gnhf #74: Simplified core runtime metadata parsing by reusing existin…
ggonzalez94 Apr 27, 2026
1074219
gnhf #75: Centralized duplicate JSON-clone helpers across core runtim…
ggonzalez94 Apr 27, 2026
9df8f7e
gnhf #76: Centralized loopback transport cloning in the CLI mock E2E …
ggonzalez94 Apr 27, 2026
0cc7e8b
gnhf #77: Simplified CLI journal list last-error projection without c…
ggonzalez94 Apr 27, 2026
09c9802
gnhf #78: Centralized tapd bound-port file parsing and persistence be…
ggonzalez94 Apr 27, 2026
ee8ab2d
gnhf #79: Centralized tapd PID file path derivation in the tapd packa…
ggonzalez94 Apr 27, 2026
e0bda37
gnhf #80: Moved tapd PID record parsing and exclusive persistence int…
ggonzalez94 Apr 27, 2026
5025f27
gnhf #81: Centralized tapd runtime state cleanup so daemon shutdown n…
ggonzalez94 Apr 27, 2026
09e0b0d
gnhf #82: Centralized tapd log-file path derivation in the tapd packa…
ggonzalez94 Apr 27, 2026
1cef09c
gnhf #83: Centralized tapd Unix socket path derivation in the tapd pa…
ggonzalez94 Apr 27, 2026
74f4bf7
gnhf #84: Centralized tapd auth-token path usage on the existing toke…
ggonzalez94 Apr 27, 2026
cb2af47
gnhf #85: Centralized fake tapd test socket/token/port path setup on …
ggonzalez94 Apr 27, 2026
1c54253
gnhf #86: Centralized tapd integration and UI test fixture runtime-st…
ggonzalez94 Apr 27, 2026
85e0ecc
gnhf #87: Centralized the remaining tapd unit-test socket path fixtur…
ggonzalez94 Apr 27, 2026
85de6b4
gnhf #88: Centralized Hermes Python plugin test tapd socket/token fix…
ggonzalez94 Apr 27, 2026
5b78dbf
gnhf #89: Centralized CLI tapd-spawn test PID and port file path usag…
ggonzalez94 Apr 27, 2026
d0fae00
gnhf #90: Centralized the remaining daemon-start test PID and port fi…
ggonzalez94 Apr 27, 2026
b79a607
gnhf #91: Centralized Hermes Python plugin tapd socket/token path der…
ggonzalez94 Apr 27, 2026
89bd5c5
gnhf #92: Centralized remaining CLI permissive object guards on the s…
ggonzalez94 Apr 27, 2026
8b70cce
gnhf #93: Centralized core object-shape guards in registration valida…
ggonzalez94 Apr 27, 2026
2cde952
gnhf #94: Centralized two remaining core object-shape checks on the s…
ggonzalez94 Apr 27, 2026
314ac4e
gnhf #95: Centralized the remaining behavior-equivalent object guards…
ggonzalez94 Apr 27, 2026
b5b7f70
gnhf #96: Simplified core runtime metadata parsing by reusing the sha…
ggonzalez94 Apr 27, 2026
d210264
gnhf #97: Centralized repeated CLI output single-array wrapper handli…
ggonzalez94 Apr 27, 2026
7ff0146
gnhf #98: Centralized the remaining CLI object/null guards in command…
ggonzalez94 Apr 27, 2026
4a18cca
gnhf #99: Centralized the Google Calendar CLI provider's ENOENT error…
ggonzalez94 Apr 27, 2026
6fbb961
gnhf #100: Centralized repeated config YAML parsing in the migrate-wa…
ggonzalez94 Apr 27, 2026
827820c
gnhf #101: Centralized app command test manifest fixture reads and wr…
ggonzalez94 Apr 27, 2026
7ca0475
gnhf #102: Centralized repeated `config.yaml` fixture path and read l…
ggonzalez94 Apr 27, 2026
5210588
gnhf #103: Centralized repeated config-loader test fixture path and Y…
ggonzalez94 Apr 27, 2026
bea1837
gnhf #104: Centralized the repeated unregistered-agent CLI config YAM…
ggonzalez94 Apr 27, 2026
ed572ab
gnhf #105: Centralized duplicated CLI test agent config YAML generati…
ggonzalez94 Apr 27, 2026
1998c2f
gnhf #106: Centralized repeated core runtime test config construction…
ggonzalez94 Apr 27, 2026
e3b05cf
gnhf #107: Centralized CLI default config path construction behind a …
ggonzalez94 Apr 27, 2026
b5d0c93
gnhf #108: Centralized `tap status` test config-path fixture construc…
ggonzalez94 Apr 27, 2026
d52c005
gnhf #109: Centralized `tap remove` test default config path construc…
ggonzalez94 Apr 27, 2026
55b9b09
gnhf #110: Centralized the remaining unregistered-agent CLI test conf…
ggonzalez94 Apr 27, 2026
75cae73
gnhf #111: Centralized default config-path assertions in the init tes…
ggonzalez94 Apr 27, 2026
60585d5
gnhf #112: Centralized the config-loader test’s default config path u…
ggonzalez94 Apr 27, 2026
d9d4c9d
gnhf #113: Centralized migrate-wallet test default config path constr…
ggonzalez94 Apr 27, 2026
434772d
gnhf #114: Centralized the app command test’s default config fixture …
ggonzalez94 Apr 27, 2026
37be952
gnhf #115: Centralized the remaining affected CLI test default config…
ggonzalez94 Apr 27, 2026
88983cc
gnhf #116: Centralized the default TAP config filename/path helper in…
ggonzalez94 Apr 27, 2026
0def25a
gnhf #117: Exported the TAP app manifest path helper from core and re…
ggonzalez94 Apr 27, 2026
1b03d87
gnhf #118: Centralized contacts and request-journal state file path c…
ggonzalez94 Apr 27, 2026
5809d92
gnhf #119: Centralized core conversation persistence path constructio…
ggonzalez94 Apr 27, 2026
4634076
gnhf #120: Centralized TAP app state path construction in core withou…
ggonzalez94 Apr 27, 2026
de70da4
gnhf #121: Centralized the remaining CLI legacy conversation director…
ggonzalez94 Apr 27, 2026
b3609bd
gnhf #122: Centralized the TAP transport-owner lock path in core and …
ggonzalez94 Apr 27, 2026
713a893
gnhf #123: Centralized default XMTP data directory path construction …
ggonzalez94 Apr 27, 2026
61bdac2
gnhf #124: Centralized the legacy raw-wallet key path used by CLI mig…
ggonzalez94 Apr 27, 2026
cd2d81b
gnhf #125: Centralized `tap status` Hermes test fixture paths on the …
ggonzalez94 Apr 27, 2026
d213a12
gnhf #126: Centralized SDK app state directory removal on the shared …
ggonzalez94 Apr 27, 2026
aaab857
gnhf #127: Centralized legacy runtime migration state paths for pendi…
ggonzalez94 Apr 27, 2026
b60930e
gnhf #128: Centralized tapd runtime-state text file read/write plumbi…
ggonzalez94 Apr 27, 2026
3ecc093
gnhf #129: Centralized tapd pidfile text reading on the shared tapd p…
ggonzalez94 Apr 27, 2026
ee3243c
gnhf #130: Centralized XMTP sync-state path construction behind the s…
ggonzalez94 Apr 27, 2026
a054f5e
gnhf #131: Centralized the CLI tapd client’s lenient optional JSON re…
ggonzalez94 Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 9 additions & 58 deletions packages/app-scheduling/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import type {
TimeSlot,
} from "trusted-agents-core";
import {
createGrantSet,
findApplicableSchedulingGrants,
findSchedulableSchedulingSlots,
mapSchedulingDecisionToResult,
parseSchedulingActionPayload,
} from "trusted-agents-core";

export async function handleSchedulingRequest(ctx: TapActionContext): Promise<TapActionResult> {
Expand Down Expand Up @@ -50,7 +52,7 @@ function handleGrantOnlyEvaluation(
): TapActionResult {
// Check if grants cover this scheduling request
const matchingGrants = findApplicableSchedulingGrants(
{ version: "tap-grants/v1", updatedAt: "", grants: ctx.peer.grantsToPeer },
createGrantSet(ctx.peer.grantsToPeer, ""),
proposal,
);

Expand Down Expand Up @@ -132,61 +134,10 @@ function handleGrantOnlyEvaluation(
}

function validatePayload(payload: Record<string, unknown>): SchedulingProposal | null {
if (payload.type !== "scheduling/propose" && payload.type !== "scheduling/counter") {
return null;
}

if (typeof payload.title !== "string" || payload.title.length === 0) {
return null;
}

if (typeof payload.duration !== "number" || payload.duration <= 0) {
return null;
}

if (!Array.isArray(payload.slots) || payload.slots.length === 0) {
return null;
}

const slots: TimeSlot[] = [];
for (const slot of payload.slots) {
if (
typeof slot !== "object" ||
slot === null ||
typeof (slot as Record<string, unknown>).start !== "string" ||
typeof (slot as Record<string, unknown>).end !== "string"
) {
return null;
}
const s = slot as { start: string; end: string };
const startDate = new Date(s.start);
const endDate = new Date(s.end);
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
return null;
}
if (startDate >= endDate) {
return null;
}
slots.push({ start: s.start, end: s.end });
}

const schedulingId =
typeof payload.schedulingId === "string" && payload.schedulingId.length > 0
? payload.schedulingId
: `sch_${Date.now()}`;

const originTimezone =
typeof payload.originTimezone === "string" && payload.originTimezone.length > 0
? payload.originTimezone
: "UTC";

return {
type: payload.type as "scheduling/propose" | "scheduling/counter",
schedulingId,
title: payload.title,
duration: payload.duration as number,
slots,
originTimezone,
...(typeof payload.note === "string" && payload.note.length > 0 ? { note: payload.note } : {}),
};
return parseSchedulingActionPayload(payload, {
defaultSchedulingId: () => `sch_${Date.now()}`,
defaultOriginTimezone: "UTC",
copySlots: true,
includeLocation: false,
});
}
5 changes: 2 additions & 3 deletions packages/app-scheduling/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineTapApp } from "trusted-agents-core";
import { defineTapApp, generateActionId } from "trusted-agents-core";
import { handleSchedulingRequest } from "./handler.js";

export { handleSchedulingRequest } from "./handler.js";
Expand Down Expand Up @@ -48,8 +48,7 @@ export function buildSchedulingPayload(params: {
}): Record<string, unknown> {
return {
type: "scheduling/propose",
schedulingId:
params.schedulingId ?? `sch_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
schedulingId: params.schedulingId ?? generateActionId("sch"),
title: params.title,
duration: params.durationMinutes,
slots: params.proposedSlots,
Expand Down
25 changes: 5 additions & 20 deletions packages/app-scheduling/test/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
SchedulingHandler,
TapActionContext,
} from "trusted-agents-core";
import { createGrantSet } from "trusted-agents-core";
import { describe, expect, it, vi } from "vitest";
import { handleSchedulingRequest } from "../src/handler.js";

Expand Down Expand Up @@ -54,16 +55,8 @@ function buildMockContext(
status: "active",
createdAt: new Date().toISOString(),
permissions: {
grantedByMe: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: overrides.grantsToPeer ?? [],
},
grantedByPeer: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: [],
},
grantedByMe: createGrantSet(overrides.grantsToPeer ?? []),
grantedByPeer: createGrantSet([]),
},
},
grantsFromPeer: [],
Expand Down Expand Up @@ -121,16 +114,8 @@ function buildMockContact(): Contact {
status: "active",
createdAt: new Date().toISOString(),
permissions: {
grantedByMe: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: [makeGrant()],
},
grantedByPeer: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: [],
},
grantedByMe: createGrantSet([makeGrant()]),
grantedByPeer: createGrantSet([]),
},
};
}
Expand Down
46 changes: 5 additions & 41 deletions packages/app-transfer/src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import type {
TapActionContext,
TapActionResult,
TransferActionRequest,
TransferAsset,
} from "trusted-agents-core";
import type { TapActionContext, TapActionResult, TransferActionRequest } from "trusted-agents-core";
import {
createGrantSet,
findApplicableTransferGrants,
isEthereumAddress,
parseTransferActionPayload,
toErrorMessage,
} from "trusted-agents-core";

Expand All @@ -25,7 +21,7 @@ export async function handleTransferRequest(ctx: TapActionContext): Promise<TapA

// Check if grants cover this transfer
const matchingGrants = findApplicableTransferGrants(
{ version: "tap-grants/v1", updatedAt: "", grants: ctx.peer.grantsToPeer },
createGrantSet(ctx.peer.grantsToPeer, ""),
request,
);

Expand Down Expand Up @@ -123,37 +119,5 @@ export async function handleTransferRequest(ctx: TapActionContext): Promise<TapA
}

function validatePayload(payload: Record<string, unknown>): TransferActionRequest | null {
if (payload.type !== "transfer/request") {
return null;
}

if (typeof payload.actionId !== "string" || payload.actionId.length === 0) {
return null;
}

if (payload.asset !== "native" && payload.asset !== "usdc") {
return null;
}

if (typeof payload.amount !== "string" || payload.amount.length === 0) {
return null;
}

if (typeof payload.chain !== "string" || payload.chain.length === 0) {
return null;
}

if (typeof payload.toAddress !== "string" || !isEthereumAddress(payload.toAddress)) {
return null;
}

return {
type: "transfer/request",
actionId: payload.actionId,
asset: payload.asset as TransferAsset,
amount: payload.amount,
chain: payload.chain,
toAddress: payload.toAddress as `0x${string}`,
note: typeof payload.note === "string" && payload.note.length > 0 ? payload.note : undefined,
};
return parseTransferActionPayload(payload);
}
4 changes: 2 additions & 2 deletions packages/app-transfer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineTapApp } from "trusted-agents-core";
import { defineTapApp, generateActionId } from "trusted-agents-core";
import { handleTransferRequest } from "./handler.js";

export { handleTransferRequest } from "./handler.js";
Expand Down Expand Up @@ -28,7 +28,7 @@ export function buildTransferPayload(params: {
}): Record<string, unknown> {
return {
type: "transfer/request",
actionId: params.actionId ?? `txn_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
actionId: params.actionId ?? generateActionId("txn"),
...params,
};
}
Expand Down
14 changes: 3 additions & 11 deletions packages/app-transfer/test/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TapActionContext } from "trusted-agents-core";
import { type TapActionContext, createGrantSet } from "trusted-agents-core";
import { describe, expect, it, vi } from "vitest";
import { handleTransferRequest } from "../src/handler.js";

Expand Down Expand Up @@ -54,16 +54,8 @@ function buildMockContext(
status: "active",
createdAt: new Date().toISOString(),
permissions: {
grantedByMe: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: overrides.grantsToPeer ?? [],
},
grantedByPeer: {
version: "tap-grants/v1",
updatedAt: new Date().toISOString(),
grants: [],
},
grantedByMe: createGrantSet(overrides.grantsToPeer ?? []),
grantedByPeer: createGrantSet([]),
},
},
grantsFromPeer: [],
Expand Down
16 changes: 12 additions & 4 deletions packages/cli/assets/hermes/plugin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ def _hermes_config_path() -> Path:
return _hermes_home() / "plugins" / "trusted-agents-tap" / "config.json"


def _tapd_socket_path(data_dir: Path) -> Path:
return data_dir / SOCKET_NAME


def _tapd_token_path(socket_path: Path) -> Path:
return socket_path.parent / TOKEN_FILE_NAME


def _load_hermes_identities() -> list[dict[str, Any]]:
"""Read the Hermes plugin config and return the list of identities.

Expand Down Expand Up @@ -183,7 +191,7 @@ def _load_auth_token(socket_path: Path) -> tuple[str | None, str | None]:
is mode 0o600, so a same-uid process that cannot read it cannot call
the daemon — this is the security boundary the unix-socket auth relies
on. Parent dir of the socket is the tapd dataDir."""
token_path = socket_path.parent / TOKEN_FILE_NAME
token_path = _tapd_token_path(socket_path)
try:
raw = token_path.read_text(encoding="utf-8").strip()
except FileNotFoundError:
Expand Down Expand Up @@ -392,7 +400,7 @@ def send_request(action: str, params: dict | None = None) -> Any:

# Strip the identity key so it doesn't leak into tapd request bodies.
dispatch_params = {k: v for k, v in params.items() if k != "identity"}
socket_path = data_dir / SOCKET_NAME
socket_path = _tapd_socket_path(data_dir)
return _dispatch(action, dispatch_params, socket_path)


Expand Down Expand Up @@ -472,7 +480,7 @@ def drain_all_identities() -> list[dict]:

if len(identities) == 0:
data_dir = _legacy_data_dir()
socket_path = data_dir / SOCKET_NAME
socket_path = _tapd_socket_path(data_dir)
drained, err = _drain_one(socket_path)
if err is not None:
return [_meta_error_notification("default", err)]
Expand All @@ -481,7 +489,7 @@ def drain_all_identities() -> list[dict]:
merged: list[dict] = []
for entry in identities:
name = entry["name"]
socket_path = Path(entry["dataDir"]) / SOCKET_NAME
socket_path = _tapd_socket_path(Path(entry["dataDir"]))
drained, err = _drain_one(socket_path)
if err is not None:
merged.append(_meta_error_notification(name, err))
Expand Down
15 changes: 7 additions & 8 deletions packages/cli/src/commands/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TapApp } from "trusted-agents-core";
import { type TapApp, isObject } from "trusted-agents-core";
import { loadConfig } from "../lib/config-loader.js";
import { handleCommandError } from "../lib/errors.js";
import { error } from "../lib/output.js";
Expand All @@ -23,12 +23,11 @@ export async function appInstallCommand(name: string, opts: GlobalOptions): Prom
const mod = await import(packageName);
const exported = mod.default ?? mod.app ?? mod;
if (
!exported ||
typeof exported !== "object" ||
typeof (exported as Record<string, unknown>).id !== "string" ||
typeof (exported as Record<string, unknown>).name !== "string" ||
typeof (exported as Record<string, unknown>).version !== "string" ||
typeof (exported as Record<string, unknown>).actions !== "object"
!isObject(exported) ||
typeof exported.id !== "string" ||
typeof exported.name !== "string" ||
typeof exported.version !== "string" ||
typeof exported.actions !== "object"
) {
error(
"VALIDATION_ERROR",
Expand All @@ -38,7 +37,7 @@ export async function appInstallCommand(name: string, opts: GlobalOptions): Prom
process.exitCode = 1;
return;
}
tapApp = exported as TapApp;
tapApp = exported as unknown as TapApp;
} catch (_importErr) {
error(
"NOT_FOUND",
Expand Down
20 changes: 11 additions & 9 deletions packages/cli/src/commands/config-set.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import YAML from "yaml";
import { writeFileAtomic } from "../lib/atomic-write.js";
import { isObject } from "trusted-agents-core";
import { readYamlFile, writeYamlFileAtomic } from "../lib/atomic-write.js";
import { resolveChainAlias } from "../lib/chains.js";
import {
resolveConfigPath,
Expand Down Expand Up @@ -61,18 +60,21 @@ export async function configSetCommand(
throw new Error(`Config file not found at ${configPath}. Run 'tap init' first.`);
}

const content = await readFile(configPath, "utf-8");
const yaml = (YAML.parse(content) as Record<string, unknown>) ?? {};
const yaml = (await readYamlFile<Record<string, unknown> | null>(configPath)) ?? {};

// Handle nested keys like xmtp.db_encryption_key
const parts = normalizeConfigPath(key);
let target: Record<string, unknown> = yaml;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i]!;
if (typeof target[part] !== "object" || target[part] === null) {
target[part] = {};
const next = target[part];
if (isObject(next)) {
target = next;
} else {
const child: Record<string, unknown> = {};
target[part] = child;
target = child;
}
target = target[part] as Record<string, unknown>;
}

const leafKey = parts[parts.length - 1]!;
Expand All @@ -81,7 +83,7 @@ export async function configSetCommand(
const resolvedValue = leafKey === "chain" ? resolveChainAlias(value) : value;
target[leafKey] = parseConfigValue(parts, resolvedValue);

await writeFileAtomic(configPath, YAML.stringify(yaml));
await writeYamlFileAtomic(configPath, yaml);

success({ key, value: target[leafKey], path: configPath }, opts, startTime);
} catch (err) {
Expand Down
Loading