@parall/agent-core 1.20.2 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +10 -0
- package/dist/gateway-base.d.ts +10 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +116 -12
- package/dist/prompt-fragments.d.ts +8 -1
- package/dist/prompt-fragments.d.ts.map +1 -1
- package/dist/prompt-fragments.js +20 -2
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/event-format.ts +7 -0
- package/src/gateway-base.ts +112 -10
- package/src/prompt-fragments.ts +31 -2
- package/src/types.ts +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CA+CzD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CASnE"}
|
package/dist/event-format.js
CHANGED
|
@@ -35,6 +35,16 @@ export function buildEventBody(event) {
|
|
|
35
35
|
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
36
36
|
lines.push("", event.body);
|
|
37
37
|
}
|
|
38
|
+
else if (event.type === "schedule") {
|
|
39
|
+
lines.push(`[Event: schedule.fired]`);
|
|
40
|
+
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
41
|
+
lines.push(`[Run: prll://${event.messageId}]`);
|
|
42
|
+
if (event.scheduledFireAt)
|
|
43
|
+
lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
44
|
+
if (event.attachedUri)
|
|
45
|
+
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
46
|
+
lines.push("", event.body);
|
|
47
|
+
}
|
|
38
48
|
else {
|
|
39
49
|
lines.push(`[Event: task.assigned]`);
|
|
40
50
|
const taskLabel = event.targetName
|
package/dist/gateway-base.d.ts
CHANGED
|
@@ -69,6 +69,16 @@ export declare class ParallAgentGateway {
|
|
|
69
69
|
private handleMessage;
|
|
70
70
|
private handleTaskAssignment;
|
|
71
71
|
private handleTaskComment;
|
|
72
|
+
/**
|
|
73
|
+
* Fetch the schedule_run snapshot, then feed it as an inbound event. 404 is
|
|
74
|
+
* tolerated and treated as "dispatched" so the caller can ack the stale
|
|
75
|
+
* delivery — see `docs/primitives/schedule.md` § "Snapshot 不变性: 内容 vs
|
|
76
|
+
* 可见性". A later PATCH on target_id / attached_to_uri legitimately revokes
|
|
77
|
+
* access to already-delivered run snapshots, and the runtime must not crash
|
|
78
|
+
* or retry forever in that case.
|
|
79
|
+
*/
|
|
80
|
+
private fetchAndHandleScheduleFire;
|
|
81
|
+
private handleScheduleFire;
|
|
72
82
|
private catchUpFromDispatch;
|
|
73
83
|
private handleHello;
|
|
74
84
|
private waitForDrain;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EACV,qBAAqB,
|
|
1
|
+
{"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway-base.js
CHANGED
|
@@ -29,6 +29,9 @@ function resolveStepTarget(event) {
|
|
|
29
29
|
if (event.targetId.startsWith("cht_")) {
|
|
30
30
|
return { target_type: "chat", target_id: event.targetId };
|
|
31
31
|
}
|
|
32
|
+
if (event.type === "schedule" || event.targetId.startsWith("sch_")) {
|
|
33
|
+
return { target_type: "schedule", target_id: event.targetId };
|
|
34
|
+
}
|
|
32
35
|
return { target_type: "", target_id: event.targetId || undefined };
|
|
33
36
|
}
|
|
34
37
|
async function fetchAllChats(client, orgId, chatInfoMap) {
|
|
@@ -150,18 +153,41 @@ export class ParallAgentGateway {
|
|
|
150
153
|
}
|
|
151
154
|
});
|
|
152
155
|
ws.on("dispatch.new", async (data) => {
|
|
153
|
-
if (data.event_type
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
if (data.event_type === "task_comment") {
|
|
157
|
+
if (!data.source_id || !data.task_id)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
const dispatched = await this.handleTaskComment(data.source_id, data.task_id, data.actor_id);
|
|
161
|
+
if (dispatched) {
|
|
162
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
else if (data.event_type === "schedule.fire") {
|
|
170
|
+
if (!data.source_id)
|
|
171
|
+
return;
|
|
172
|
+
try {
|
|
173
|
+
const dispatched = await this.fetchAndHandleScheduleFire(data.source_id, data.actor_id);
|
|
174
|
+
if (dispatched) {
|
|
175
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
183
|
+
// Truly unknown event_type — log so a newly-added dispatch type
|
|
184
|
+
// not yet wired here surfaces during runtime testing. "message"
|
|
185
|
+
// and "task_assign" are deliberately excluded: dispatch.new
|
|
186
|
+
// carries them too, but they are owned by dedicated WS handlers
|
|
187
|
+
// (message.new, task.assigned) above and would otherwise spam
|
|
188
|
+
// info-level logs for every inbound chat message / task
|
|
189
|
+
// assignment on a busy agent.
|
|
190
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) — no-op`);
|
|
165
191
|
}
|
|
166
192
|
});
|
|
167
193
|
this.opts.log?.info(`parall[${this.opts.accountId}]: connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
|
|
@@ -241,8 +267,14 @@ export class ParallAgentGateway {
|
|
|
241
267
|
target_type: target.target_type,
|
|
242
268
|
target_id: target.target_id,
|
|
243
269
|
content: {
|
|
244
|
-
trigger_type: event.type === "task" ? "task_assign" :
|
|
245
|
-
|
|
270
|
+
trigger_type: event.type === "task" ? "task_assign" :
|
|
271
|
+
event.type === "task_comment" ? "task_comment" :
|
|
272
|
+
event.type === "schedule" ? "schedule_fire" :
|
|
273
|
+
"mention",
|
|
274
|
+
trigger_ref: event.type === "task" ? { task_id: event.targetId } :
|
|
275
|
+
event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } :
|
|
276
|
+
event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } :
|
|
277
|
+
{ message_id: event.messageId },
|
|
246
278
|
sender_id: event.senderId,
|
|
247
279
|
sender_name: event.senderName,
|
|
248
280
|
summary: event.body.substring(0, 200),
|
|
@@ -870,6 +902,75 @@ export class ParallAgentGateway {
|
|
|
870
902
|
}
|
|
871
903
|
return dispatched;
|
|
872
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* Fetch the schedule_run snapshot, then feed it as an inbound event. 404 is
|
|
907
|
+
* tolerated and treated as "dispatched" so the caller can ack the stale
|
|
908
|
+
* delivery — see `docs/primitives/schedule.md` § "Snapshot 不变性: 内容 vs
|
|
909
|
+
* 可见性". A later PATCH on target_id / attached_to_uri legitimately revokes
|
|
910
|
+
* access to already-delivered run snapshots, and the runtime must not crash
|
|
911
|
+
* or retry forever in that case.
|
|
912
|
+
*/
|
|
913
|
+
async fetchAndHandleScheduleFire(runId, actorId) {
|
|
914
|
+
let run = null;
|
|
915
|
+
try {
|
|
916
|
+
run = await this.opts.client.getScheduleRun(this.opts.config.org_id, runId);
|
|
917
|
+
}
|
|
918
|
+
catch (err) {
|
|
919
|
+
const status = err?.status;
|
|
920
|
+
if (status === 404) {
|
|
921
|
+
// Contract: ack + warn log + drop. A post-fire PATCH on
|
|
922
|
+
// target_id / attached_to_uri can legitimately revoke access to an
|
|
923
|
+
// already-delivered run — this is expected but operationally
|
|
924
|
+
// visible, so warn (not info) keeps it findable during incident
|
|
925
|
+
// triage. See docs/primitives/schedule.md § Snapshot 不变性.
|
|
926
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: schedule run ${runId} not accessible (404), acking stale dispatch`);
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: schedule run fetch failed for ${runId}, leaving pending: ${String(err)}`);
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
if (!run)
|
|
933
|
+
return true;
|
|
934
|
+
return this.handleScheduleFire(run, actorId);
|
|
935
|
+
}
|
|
936
|
+
async handleScheduleFire(run, actorId) {
|
|
937
|
+
if (this.shuttingDown)
|
|
938
|
+
return false; // drain window — let server requeue via catch-up
|
|
939
|
+
const dedupeKey = `schedule_run:${run.id}`;
|
|
940
|
+
if (this.dispatchedTasks.has(dedupeKey))
|
|
941
|
+
return false;
|
|
942
|
+
this.dispatchedTasks.add(dedupeKey);
|
|
943
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: schedule fired: ${run.id} (schedule ${run.schedule_id})`);
|
|
944
|
+
const event = {
|
|
945
|
+
type: "schedule",
|
|
946
|
+
// Route by schedule_id (not attached chat_id) so concurrent fires of
|
|
947
|
+
// different schedules can fork independently — matches the PR1 primitive
|
|
948
|
+
// design where "schedule triggers; target decides response" and fire
|
|
949
|
+
// semantics are independent of any attached conversation.
|
|
950
|
+
targetId: run.schedule_id,
|
|
951
|
+
targetType: "schedule",
|
|
952
|
+
senderId: actorId ?? "system",
|
|
953
|
+
senderName: "schedule",
|
|
954
|
+
messageId: run.id,
|
|
955
|
+
body: run.fired_description ?? "",
|
|
956
|
+
scheduledFireAt: run.scheduled_fire_at,
|
|
957
|
+
attachedUri: run.fired_attached_uri ?? undefined,
|
|
958
|
+
ackSourceType: "schedule_run",
|
|
959
|
+
ackSourceId: run.id,
|
|
960
|
+
};
|
|
961
|
+
let dispatched;
|
|
962
|
+
try {
|
|
963
|
+
dispatched = await this.handleInboundEvent(event);
|
|
964
|
+
}
|
|
965
|
+
catch (err) {
|
|
966
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
967
|
+
throw err;
|
|
968
|
+
}
|
|
969
|
+
if (!dispatched) {
|
|
970
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
971
|
+
}
|
|
972
|
+
return dispatched;
|
|
973
|
+
}
|
|
873
974
|
async catchUpFromDispatch(coldStart = false) {
|
|
874
975
|
const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
|
|
875
976
|
let cursor;
|
|
@@ -926,6 +1027,9 @@ export class ParallAgentGateway {
|
|
|
926
1027
|
else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
927
1028
|
dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id);
|
|
928
1029
|
}
|
|
1030
|
+
else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
1031
|
+
dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
|
|
1032
|
+
}
|
|
929
1033
|
else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
930
1034
|
if (!this.tryClaimMessage(item.source_id))
|
|
931
1035
|
continue;
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
* audiences, no duplication — this teaches agents HOW to work on Parall, the
|
|
10
10
|
* design docs explain WHY the system works the way it does.
|
|
11
11
|
*/
|
|
12
|
+
export interface AgentIdentity {
|
|
13
|
+
userId: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function buildIdentity(agent?: AgentIdentity): string;
|
|
18
|
+
/** @deprecated Use buildIdentity() instead. Kept for backward compat during migration. */
|
|
12
19
|
export declare const PRLL_IDENTITY = "## You on Parall\n\nParall is a shared workspace where humans and agents work side by side as equals.\nYou are a participant here, not a service. You hold tasks, own decisions, and are\naccountable for the work you take on \u2014 the same way a human teammate is.\n\nThe people and agents around you are collaborators, not users to serve. Be honest,\nbe direct, and care about the outcome of the work \u2014 not just the request in front\nof you.";
|
|
13
20
|
export declare const PRLL_BEHAVIOR = "## How to work here\n\n### Move work forward\nDon't wait for instructions. If you see the next step, take it. If something is\nambiguous, clarify once and proceed. If you're blocked, say what's blocking you\n\u2014 don't go silent. Initiative is expected.\n\n### Work in the open\nNothing you do exists until the system can see it. Your progress, decisions,\nblockers, and results need to live in tasks, comments, messages, or wiki pages\n\u2014 otherwise the organization is blind to your work, and so is the next agent\nwho picks up where you left off. Leave traces as you go, not at the end.\n\nFor non-trivial work: create or claim a task, mark it `in_progress`, comment\nwhen status materially changes, close it when done, and link the origin that\ntriggered it. Details: load the `parall-tasks` skill.\n\n### Communicate like a teammate\nMatch the conversation \u2014 concise in chat, thorough in docs, plain language over\njargon. Say what matters; stop when you're done. Don't narrate every tool call\nor pad replies to seem thorough.\n\n### Respect what's shared\nYou have broad latitude inside your own work. But actions that are visible to\nothers, hard to reverse, or touch shared state \u2014 sending DMs, editing shared\nwiki, reassigning others' tasks, deleting content \u2014 pause and confirm before\nacting, unless you've been explicitly authorized.\n\n### Shared workspace\nOther agents share this workspace. Before starting work, check whether someone\n\u2014 human or agent \u2014 has already picked it up. Coordination beats racing.\n\n### When in doubt\nPrefer asking over guessing. Prefer \"I don't know\" over fabricating. Your\ncredibility is what you bring to the workspace \u2014 protect it.";
|
|
14
|
-
export declare const PRLL_REFERENCE_GUIDE = "## Parall References\n\nEvery entity on Parall has a `prll://` URI. Use these URIs to link related\nentities when you create or update tasks, comments, messages, and wiki files.\n\nAll three forms work \u2014 pick whichever fits:\n\n prll://tsk_abc bare URI (auto-linked)\n [](prll://tsk_abc) empty context (renders resolved title)\n [relevant context](prll://tsk_abc) with author annotation\n\nBare URIs and empty-context refs are preferred in most cases \u2014 the platform\nresolves and renders the entity title automatically.\n\n### URI format\n\n`prll://` follows standard URI structure: `scheme://authority/path?query#fragment`.\n\n**Entities** \u2014 the entity ID is the authority:\n\n prll://usr_xxx user prll://prj_xxx project\n prll://tsk_xxx task prll://wik_xxx wiki\n prll://msg_xxx message prll://tcm_xxx task comment\n prll://cht_xxx chat prll://ase_xxx agent session\n prll://att_xxx attachment\n\n**Wiki** \u2014 path is file path, fragment is a typed anchor:\n\n prll://wik_xxx/docs/guide.md file\n prll://wik_xxx/docs/guide.md#h=Auth::OAuth heading (:: = hierarchy)\n prll://wik_xxx/src/auth.go?rev=<sha>#l=42-58 line range (revision-pinned)\n\n Anchor types: `h=` heading, `l=` line/range, `s=` symbol.\n Line anchors in persistent content require `?rev=<full-40-char-sha>`.\n\n**Chat message range**:\n\n prll://cht_xxx#range=msg_01HA,msg_01HZ\n\n**Field access** \u2014 path selects a field (omit to reference the entity itself):\n\n prll://tsk_xxx/description#Implementation heading within task description\n\n### Reading context on demand\n\nAn event only carries the single triggering message. If you're mentioned in a\ngroup chat and lack context, pull what you need from the chat \u2014 don't guess:\n\n npx --yes @parall/cli@latest messages list cht_xxx --limit 20 --before msg_xxx\n npx --yes @parall/cli@latest messages get msg_xxx\n npx --yes @parall/cli@latest chats get cht_xxx\n\nRule of thumb: in a group chat mention, the conversation that led up to you\nbeing called almost always matters \u2014 read it before replying. In a DM, your\nsession already has continuity, so skip the fetch unless something is unclear.\n\nSame pattern for any other entity referenced in the event: `tasks get`,\n`projects get`, `users get`, `chats get`. Follow the reflink, don't ask.\n\n### File attachments\n\nMessages may include attachments. They appear in events as:\n\n [Attachment: prll://att_xxx | image/png | 1.2MB | screenshot.png]\n\nTo download an attachment, use the CLI:\n\n npx @parall/cli@latest files download att_xxx --output /tmp/screenshot.png\n\nTo send a file:\n\n npx @parall/cli@latest messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\nOr upload first and reuse across chats:\n\n npx @parall/cli@latest files upload /tmp/report.pdf\n npx @parall/cli@latest messages send prll://cht_aaa --attachment att_yyy --text \"Report\"\n npx @parall/cli@latest messages send prll://cht_bbb --attachment att_yyy --text \"FYI\"\n\n### When to reference\n\n- **Origin** \u2014 always link the message or task that triggered your work\n- **Design docs / wiki** \u2014 link specs and guides relevant to the work\n- **Related tasks** \u2014 link parent, sibling, or blocking tasks\n- **People** \u2014 link assignees or stakeholders when mentioning them\n- **Conversations** \u2014 link a chat or message range as context\n\n### Why this matters\n\nOther agents and humans read your output. References build a navigable context graph \u2014\nin multi-agent workflows, your references are the map that the next agent follows.";
|
|
21
|
+
export declare const PRLL_REFERENCE_GUIDE = "## Parall References\n\nEvery entity on Parall has a `prll://` URI. Use these URIs to link related\nentities when you create or update tasks, comments, messages, and wiki files.\n\nAll three forms work \u2014 pick whichever fits:\n\n prll://tsk_abc bare URI (auto-linked)\n [](prll://tsk_abc) empty context (renders resolved title)\n [relevant context](prll://tsk_abc) with author annotation\n\nBare URIs and empty-context refs are preferred in most cases \u2014 the platform\nresolves and renders the entity title automatically.\n\n### URI format\n\n`prll://` follows standard URI structure: `scheme://authority/path?query#fragment`.\n\n**Entities** \u2014 the entity ID is the authority:\n\n prll://usr_xxx user prll://prj_xxx project\n prll://tsk_xxx task prll://wik_xxx wiki\n prll://msg_xxx message prll://tcm_xxx task comment\n prll://cht_xxx chat prll://ase_xxx agent session\n prll://att_xxx attachment prll://sch_xxx schedule\n prll://srn_xxx schedule run\n\n**Wiki** \u2014 path is file path, fragment is a typed anchor:\n\n prll://wik_xxx/docs/guide.md file\n prll://wik_xxx/docs/guide.md#h=Auth::OAuth heading (:: = hierarchy)\n prll://wik_xxx/src/auth.go?rev=<sha>#l=42-58 line range (revision-pinned)\n\n Anchor types: `h=` heading, `l=` line/range, `s=` symbol.\n Line anchors in persistent content require `?rev=<full-40-char-sha>`.\n\n**Chat message range**:\n\n prll://cht_xxx#range=msg_01HA,msg_01HZ\n\n**Field access** \u2014 path selects a field (omit to reference the entity itself):\n\n prll://tsk_xxx/description#Implementation heading within task description\n\n### Reading context on demand\n\nAn event only carries the single triggering message. If you're mentioned in a\ngroup chat and lack context, pull what you need from the chat \u2014 don't guess:\n\n npx --yes @parall/cli@latest messages list cht_xxx --limit 20 --before msg_xxx\n npx --yes @parall/cli@latest messages get msg_xxx\n npx --yes @parall/cli@latest chats get cht_xxx\n\nRule of thumb: in a group chat mention, the conversation that led up to you\nbeing called almost always matters \u2014 read it before replying. In a DM, your\nsession already has continuity, so skip the fetch unless something is unclear.\n\nSame pattern for any other entity referenced in the event: `tasks get`,\n`projects get`, `users get`, `chats get`. Follow the reflink, don't ask.\n\n### File attachments\n\nMessages may include attachments. They appear in events as:\n\n [Attachment: prll://att_xxx | image/png | 1.2MB | screenshot.png]\n\nTo download an attachment, use the CLI:\n\n npx @parall/cli@latest files download att_xxx --output /tmp/screenshot.png\n\nTo send a file:\n\n npx @parall/cli@latest messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\nOr upload first and reuse across chats:\n\n npx @parall/cli@latest files upload /tmp/report.pdf\n npx @parall/cli@latest messages send prll://cht_aaa --attachment att_yyy --text \"Report\"\n npx @parall/cli@latest messages send prll://cht_bbb --attachment att_yyy --text \"FYI\"\n\n### When to reference\n\n- **Origin** \u2014 always link the message or task that triggered your work\n- **Design docs / wiki** \u2014 link specs and guides relevant to the work\n- **Related tasks** \u2014 link parent, sibling, or blocking tasks\n- **People** \u2014 link assignees or stakeholders when mentioning them\n- **Conversations** \u2014 link a chat or message range as context\n\n### Why this matters\n\nOther agents and humans read your output. References build a navigable context graph \u2014\nin multi-agent workflows, your references are the map that the next agent follows.";
|
|
15
22
|
//# sourceMappingURL=prompt-fragments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-fragments.d.ts","sourceRoot":"","sources":["../src/prompt-fragments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"prompt-fragments.d.ts","sourceRoot":"","sources":["../src/prompt-fragments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgBD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAa3D;AAED,0FAA0F;AAC1F,eAAO,MAAM,aAAa,mcAAqB,CAAC;AAEhD,eAAO,MAAM,aAAa,4rDAkCmC,CAAC;AAE9D,eAAO,MAAM,oBAAoB,03HA2FkD,CAAC"}
|
package/dist/prompt-fragments.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* audiences, no duplication — this teaches agents HOW to work on Parall, the
|
|
10
10
|
* design docs explain WHY the system works the way it does.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
const PRLL_IDENTITY_BASE = `## You on Parall
|
|
13
13
|
|
|
14
14
|
Parall is a shared workspace where humans and agents work side by side as equals.
|
|
15
15
|
You are a participant here, not a service. You hold tasks, own decisions, and are
|
|
@@ -18,6 +18,23 @@ accountable for the work you take on — the same way a human teammate is.
|
|
|
18
18
|
The people and agents around you are collaborators, not users to serve. Be honest,
|
|
19
19
|
be direct, and care about the outcome of the work — not just the request in front
|
|
20
20
|
of you.`;
|
|
21
|
+
function sanitizeProfileField(value) {
|
|
22
|
+
return value.replace(/[\r\n]+/g, " ").replace(/`/g, "'").trim();
|
|
23
|
+
}
|
|
24
|
+
export function buildIdentity(agent) {
|
|
25
|
+
if (!agent)
|
|
26
|
+
return PRLL_IDENTITY_BASE;
|
|
27
|
+
const name = sanitizeProfileField(agent.displayName);
|
|
28
|
+
const lines = [PRLL_IDENTITY_BASE, "", "### Your Parall Identity", ""];
|
|
29
|
+
lines.push(`You are **${name}** (\`prll://${agent.userId}\`).`);
|
|
30
|
+
if (agent.description) {
|
|
31
|
+
lines.push(`Description: ${sanitizeProfileField(agent.description)}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push("", `When you see \`${agent.userId}\` or \`prll://${agent.userId}\` in messages, mentions, or events — that's you.`);
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
/** @deprecated Use buildIdentity() instead. Kept for backward compat during migration. */
|
|
37
|
+
export const PRLL_IDENTITY = PRLL_IDENTITY_BASE;
|
|
21
38
|
export const PRLL_BEHAVIOR = `## How to work here
|
|
22
39
|
|
|
23
40
|
### Move work forward
|
|
@@ -77,7 +94,8 @@ resolves and renders the entity title automatically.
|
|
|
77
94
|
prll://tsk_xxx task prll://wik_xxx wiki
|
|
78
95
|
prll://msg_xxx message prll://tcm_xxx task comment
|
|
79
96
|
prll://cht_xxx chat prll://ase_xxx agent session
|
|
80
|
-
prll://att_xxx attachment
|
|
97
|
+
prll://att_xxx attachment prll://sch_xxx schedule
|
|
98
|
+
prll://srn_xxx schedule run
|
|
81
99
|
|
|
82
100
|
**Wiki** — path is file path, fragment is a typed anchor:
|
|
83
101
|
|
package/dist/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type DispatchState = {
|
|
|
18
18
|
};
|
|
19
19
|
/** Normalized inbound event from Parall. */
|
|
20
20
|
export type ParallEvent = {
|
|
21
|
-
type: "message" | "task" | "task_comment";
|
|
21
|
+
type: "message" | "task" | "task_comment" | "schedule";
|
|
22
22
|
targetId: string;
|
|
23
23
|
targetName?: string;
|
|
24
24
|
targetType?: string;
|
|
@@ -34,7 +34,11 @@ export type ParallEvent = {
|
|
|
34
34
|
fileSize: number;
|
|
35
35
|
mimeType: string;
|
|
36
36
|
}>;
|
|
37
|
-
|
|
37
|
+
/** Fire-time snapshot timestamp, used for schedule events. */
|
|
38
|
+
scheduledFireAt?: string;
|
|
39
|
+
/** Fire-time attached_to_uri snapshot, used for schedule events. */
|
|
40
|
+
attachedUri?: string;
|
|
41
|
+
ackSourceType?: "message" | "task_activity" | "comment" | "schedule_run";
|
|
38
42
|
ackSourceId?: string;
|
|
39
43
|
};
|
|
40
44
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAC;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Shared agent runtime orchestration helpers for Parall",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@parall/sdk": "1.
|
|
25
|
+
"@parall/sdk": "1.22.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.0.0",
|
package/src/event-format.ts
CHANGED
|
@@ -34,6 +34,13 @@ export function buildEventBody(event: ParallEvent): string {
|
|
|
34
34
|
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
35
35
|
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
36
36
|
lines.push("", event.body);
|
|
37
|
+
} else if (event.type === "schedule") {
|
|
38
|
+
lines.push(`[Event: schedule.fired]`);
|
|
39
|
+
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
40
|
+
lines.push(`[Run: prll://${event.messageId}]`);
|
|
41
|
+
if (event.scheduledFireAt) lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
42
|
+
if (event.attachedUri) lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
43
|
+
lines.push("", event.body);
|
|
37
44
|
} else {
|
|
38
45
|
lines.push(`[Event: task.assigned]`);
|
|
39
46
|
const taskLabel = event.targetName
|
package/src/gateway-base.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
DispatchNewData,
|
|
12
12
|
HelloData,
|
|
13
13
|
MessageNewData,
|
|
14
|
+
ScheduleRun,
|
|
14
15
|
Task,
|
|
15
16
|
TaskAssignedData,
|
|
16
17
|
TextContent,
|
|
@@ -124,6 +125,9 @@ function resolveStepTarget(event: ParallEvent): { target_type: string; target_id
|
|
|
124
125
|
if (event.targetId.startsWith("cht_")) {
|
|
125
126
|
return { target_type: "chat", target_id: event.targetId };
|
|
126
127
|
}
|
|
128
|
+
if (event.type === "schedule" || event.targetId.startsWith("sch_")) {
|
|
129
|
+
return { target_type: "schedule", target_id: event.targetId };
|
|
130
|
+
}
|
|
127
131
|
return { target_type: "", target_id: event.targetId || undefined };
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -257,15 +261,35 @@ export class ParallAgentGateway {
|
|
|
257
261
|
});
|
|
258
262
|
|
|
259
263
|
ws.on("dispatch.new", async (data: DispatchNewData) => {
|
|
260
|
-
if (data.event_type
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
if (data.event_type === "task_comment") {
|
|
265
|
+
if (!data.source_id || !data.task_id) return;
|
|
266
|
+
try {
|
|
267
|
+
const dispatched = await this.handleTaskComment(data.source_id, data.task_id, data.actor_id);
|
|
268
|
+
if (dispatched) {
|
|
269
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
266
273
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
274
|
+
} else if (data.event_type === "schedule.fire") {
|
|
275
|
+
if (!data.source_id) return;
|
|
276
|
+
try {
|
|
277
|
+
const dispatched = await this.fetchAndHandleScheduleFire(data.source_id, data.actor_id);
|
|
278
|
+
if (dispatched) {
|
|
279
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
this.opts.log?.error(`parall[${this.opts.accountId}]: schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
283
|
+
}
|
|
284
|
+
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
285
|
+
// Truly unknown event_type — log so a newly-added dispatch type
|
|
286
|
+
// not yet wired here surfaces during runtime testing. "message"
|
|
287
|
+
// and "task_assign" are deliberately excluded: dispatch.new
|
|
288
|
+
// carries them too, but they are owned by dedicated WS handlers
|
|
289
|
+
// (message.new, task.assigned) above and would otherwise spam
|
|
290
|
+
// info-level logs for every inbound chat message / task
|
|
291
|
+
// assignment on a busy agent.
|
|
292
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) — no-op`);
|
|
269
293
|
}
|
|
270
294
|
});
|
|
271
295
|
|
|
@@ -346,8 +370,16 @@ export class ParallAgentGateway {
|
|
|
346
370
|
target_type: target.target_type,
|
|
347
371
|
target_id: target.target_id,
|
|
348
372
|
content: {
|
|
349
|
-
trigger_type:
|
|
350
|
-
|
|
373
|
+
trigger_type:
|
|
374
|
+
event.type === "task" ? "task_assign" :
|
|
375
|
+
event.type === "task_comment" ? "task_comment" :
|
|
376
|
+
event.type === "schedule" ? "schedule_fire" :
|
|
377
|
+
"mention",
|
|
378
|
+
trigger_ref:
|
|
379
|
+
event.type === "task" ? { task_id: event.targetId } :
|
|
380
|
+
event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } :
|
|
381
|
+
event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } :
|
|
382
|
+
{ message_id: event.messageId },
|
|
351
383
|
sender_id: event.senderId,
|
|
352
384
|
sender_name: event.senderName,
|
|
353
385
|
summary: event.body.substring(0, 200),
|
|
@@ -1001,6 +1033,74 @@ export class ParallAgentGateway {
|
|
|
1001
1033
|
return dispatched;
|
|
1002
1034
|
}
|
|
1003
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* Fetch the schedule_run snapshot, then feed it as an inbound event. 404 is
|
|
1038
|
+
* tolerated and treated as "dispatched" so the caller can ack the stale
|
|
1039
|
+
* delivery — see `docs/primitives/schedule.md` § "Snapshot 不变性: 内容 vs
|
|
1040
|
+
* 可见性". A later PATCH on target_id / attached_to_uri legitimately revokes
|
|
1041
|
+
* access to already-delivered run snapshots, and the runtime must not crash
|
|
1042
|
+
* or retry forever in that case.
|
|
1043
|
+
*/
|
|
1044
|
+
private async fetchAndHandleScheduleFire(runId: string, actorId: string | null): Promise<boolean> {
|
|
1045
|
+
let run: ScheduleRun | null = null;
|
|
1046
|
+
try {
|
|
1047
|
+
run = await this.opts.client.getScheduleRun(this.opts.config.org_id, runId);
|
|
1048
|
+
} catch (err: unknown) {
|
|
1049
|
+
const status = (err as { status?: number })?.status;
|
|
1050
|
+
if (status === 404) {
|
|
1051
|
+
// Contract: ack + warn log + drop. A post-fire PATCH on
|
|
1052
|
+
// target_id / attached_to_uri can legitimately revoke access to an
|
|
1053
|
+
// already-delivered run — this is expected but operationally
|
|
1054
|
+
// visible, so warn (not info) keeps it findable during incident
|
|
1055
|
+
// triage. See docs/primitives/schedule.md § Snapshot 不变性.
|
|
1056
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: schedule run ${runId} not accessible (404), acking stale dispatch`);
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
this.opts.log?.warn(`parall[${this.opts.accountId}]: schedule run fetch failed for ${runId}, leaving pending: ${String(err)}`);
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
if (!run) return true;
|
|
1063
|
+
return this.handleScheduleFire(run, actorId);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
private async handleScheduleFire(run: ScheduleRun, actorId: string | null): Promise<boolean> {
|
|
1067
|
+
if (this.shuttingDown) return false; // drain window — let server requeue via catch-up
|
|
1068
|
+
const dedupeKey = `schedule_run:${run.id}`;
|
|
1069
|
+
if (this.dispatchedTasks.has(dedupeKey)) return false;
|
|
1070
|
+
this.dispatchedTasks.add(dedupeKey);
|
|
1071
|
+
this.opts.log?.info(`parall[${this.opts.accountId}]: schedule fired: ${run.id} (schedule ${run.schedule_id})`);
|
|
1072
|
+
|
|
1073
|
+
const event: ParallEvent = {
|
|
1074
|
+
type: "schedule",
|
|
1075
|
+
// Route by schedule_id (not attached chat_id) so concurrent fires of
|
|
1076
|
+
// different schedules can fork independently — matches the PR1 primitive
|
|
1077
|
+
// design where "schedule triggers; target decides response" and fire
|
|
1078
|
+
// semantics are independent of any attached conversation.
|
|
1079
|
+
targetId: run.schedule_id,
|
|
1080
|
+
targetType: "schedule",
|
|
1081
|
+
senderId: actorId ?? "system",
|
|
1082
|
+
senderName: "schedule",
|
|
1083
|
+
messageId: run.id,
|
|
1084
|
+
body: run.fired_description ?? "",
|
|
1085
|
+
scheduledFireAt: run.scheduled_fire_at,
|
|
1086
|
+
attachedUri: run.fired_attached_uri ?? undefined,
|
|
1087
|
+
ackSourceType: "schedule_run",
|
|
1088
|
+
ackSourceId: run.id,
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
let dispatched: boolean;
|
|
1092
|
+
try {
|
|
1093
|
+
dispatched = await this.handleInboundEvent(event);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
1096
|
+
throw err;
|
|
1097
|
+
}
|
|
1098
|
+
if (!dispatched) {
|
|
1099
|
+
this.dispatchedTasks.delete(dedupeKey);
|
|
1100
|
+
}
|
|
1101
|
+
return dispatched;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1004
1104
|
private async catchUpFromDispatch(coldStart = false) {
|
|
1005
1105
|
const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
|
|
1006
1106
|
let cursor: string | undefined;
|
|
@@ -1053,6 +1153,8 @@ export class ParallAgentGateway {
|
|
|
1053
1153
|
}
|
|
1054
1154
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
1055
1155
|
dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id);
|
|
1156
|
+
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
1157
|
+
dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
|
|
1056
1158
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
1057
1159
|
if (!this.tryClaimMessage(item.source_id)) continue;
|
|
1058
1160
|
let msg: Awaited<ReturnType<typeof this.opts.client.getMessage>> | null = null;
|
package/src/prompt-fragments.ts
CHANGED
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
* design docs explain WHY the system works the way it does.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export interface AgentIdentity {
|
|
14
|
+
userId: string;
|
|
15
|
+
displayName: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PRLL_IDENTITY_BASE = `## You on Parall
|
|
14
20
|
|
|
15
21
|
Parall is a shared workspace where humans and agents work side by side as equals.
|
|
16
22
|
You are a participant here, not a service. You hold tasks, own decisions, and are
|
|
@@ -20,6 +26,28 @@ The people and agents around you are collaborators, not users to serve. Be hones
|
|
|
20
26
|
be direct, and care about the outcome of the work — not just the request in front
|
|
21
27
|
of you.`;
|
|
22
28
|
|
|
29
|
+
function sanitizeProfileField(value: string): string {
|
|
30
|
+
return value.replace(/[\r\n]+/g, " ").replace(/`/g, "'").trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildIdentity(agent?: AgentIdentity): string {
|
|
34
|
+
if (!agent) return PRLL_IDENTITY_BASE;
|
|
35
|
+
const name = sanitizeProfileField(agent.displayName);
|
|
36
|
+
const lines = [PRLL_IDENTITY_BASE, "", "### Your Parall Identity", ""];
|
|
37
|
+
lines.push(`You are **${name}** (\`prll://${agent.userId}\`).`);
|
|
38
|
+
if (agent.description) {
|
|
39
|
+
lines.push(`Description: ${sanitizeProfileField(agent.description)}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push(
|
|
42
|
+
"",
|
|
43
|
+
`When you see \`${agent.userId}\` or \`prll://${agent.userId}\` in messages, mentions, or events — that's you.`,
|
|
44
|
+
);
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @deprecated Use buildIdentity() instead. Kept for backward compat during migration. */
|
|
49
|
+
export const PRLL_IDENTITY = PRLL_IDENTITY_BASE;
|
|
50
|
+
|
|
23
51
|
export const PRLL_BEHAVIOR = `## How to work here
|
|
24
52
|
|
|
25
53
|
### Move work forward
|
|
@@ -80,7 +108,8 @@ resolves and renders the entity title automatically.
|
|
|
80
108
|
prll://tsk_xxx task prll://wik_xxx wiki
|
|
81
109
|
prll://msg_xxx message prll://tcm_xxx task comment
|
|
82
110
|
prll://cht_xxx chat prll://ase_xxx agent session
|
|
83
|
-
prll://att_xxx attachment
|
|
111
|
+
prll://att_xxx attachment prll://sch_xxx schedule
|
|
112
|
+
prll://srn_xxx schedule run
|
|
84
113
|
|
|
85
114
|
**Wiki** — path is file path, fragment is a typed anchor:
|
|
86
115
|
|
package/src/types.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type DispatchState = {
|
|
|
16
16
|
|
|
17
17
|
/** Normalized inbound event from Parall. */
|
|
18
18
|
export type ParallEvent = {
|
|
19
|
-
type: "message" | "task" | "task_comment";
|
|
19
|
+
type: "message" | "task" | "task_comment" | "schedule";
|
|
20
20
|
targetId: string;
|
|
21
21
|
targetName?: string;
|
|
22
22
|
targetType?: string;
|
|
@@ -32,6 +32,10 @@ export type ParallEvent = {
|
|
|
32
32
|
fileSize: number;
|
|
33
33
|
mimeType: string;
|
|
34
34
|
}>;
|
|
35
|
-
|
|
35
|
+
/** Fire-time snapshot timestamp, used for schedule events. */
|
|
36
|
+
scheduledFireAt?: string;
|
|
37
|
+
/** Fire-time attached_to_uri snapshot, used for schedule events. */
|
|
38
|
+
attachedUri?: string;
|
|
39
|
+
ackSourceType?: "message" | "task_activity" | "comment" | "schedule_run";
|
|
36
40
|
ackSourceId?: string;
|
|
37
41
|
};
|