agent-sin 0.1.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.
Files changed (150) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -0
  4. package/assets/logo.png +0 -0
  5. package/builtin-skills/_shared/_models_lib.py +227 -0
  6. package/builtin-skills/_shared/_profile_lib.py +98 -0
  7. package/builtin-skills/_shared/_schedules_lib.py +313 -0
  8. package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
  9. package/builtin-skills/_shared/i18n.py +26 -0
  10. package/builtin-skills/memo-delete/main.py +155 -0
  11. package/builtin-skills/memo-delete/skill.yaml +57 -0
  12. package/builtin-skills/memo-index/main.py +178 -0
  13. package/builtin-skills/memo-index/skill.yaml +53 -0
  14. package/builtin-skills/memo-save/README.md +5 -0
  15. package/builtin-skills/memo-save/main.py +74 -0
  16. package/builtin-skills/memo-save/skill.yaml +52 -0
  17. package/builtin-skills/memo-search/README.md +10 -0
  18. package/builtin-skills/memo-search/main.py +97 -0
  19. package/builtin-skills/memo-search/skill.yaml +51 -0
  20. package/builtin-skills/memo-vector-search/main.py +121 -0
  21. package/builtin-skills/memo-vector-search/skill.yaml +53 -0
  22. package/builtin-skills/model-add/main.py +180 -0
  23. package/builtin-skills/model-add/skill.yaml +112 -0
  24. package/builtin-skills/model-list/main.py +93 -0
  25. package/builtin-skills/model-list/skill.yaml +48 -0
  26. package/builtin-skills/model-set/main.py +123 -0
  27. package/builtin-skills/model-set/skill.yaml +69 -0
  28. package/builtin-skills/profile-delete/_profile_lib.py +98 -0
  29. package/builtin-skills/profile-delete/main.py +98 -0
  30. package/builtin-skills/profile-delete/skill.yaml +64 -0
  31. package/builtin-skills/profile-edit/_profile_lib.py +98 -0
  32. package/builtin-skills/profile-edit/main.py +97 -0
  33. package/builtin-skills/profile-edit/skill.yaml +72 -0
  34. package/builtin-skills/profile-save/main.py +52 -0
  35. package/builtin-skills/profile-save/skill.yaml +69 -0
  36. package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
  37. package/builtin-skills/schedule-add/main.py +137 -0
  38. package/builtin-skills/schedule-add/skill.yaml +94 -0
  39. package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
  40. package/builtin-skills/schedule-list/main.py +86 -0
  41. package/builtin-skills/schedule-list/skill.yaml +45 -0
  42. package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
  43. package/builtin-skills/schedule-remove/main.py +69 -0
  44. package/builtin-skills/schedule-remove/skill.yaml +49 -0
  45. package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
  46. package/builtin-skills/schedule-toggle/main.py +78 -0
  47. package/builtin-skills/schedule-toggle/skill.yaml +61 -0
  48. package/builtin-skills/skills-disable/main.py +63 -0
  49. package/builtin-skills/skills-disable/skill.yaml +52 -0
  50. package/builtin-skills/skills-enable/main.py +62 -0
  51. package/builtin-skills/skills-enable/skill.yaml +51 -0
  52. package/builtin-skills/todo-add/main.py +68 -0
  53. package/builtin-skills/todo-add/skill.yaml +53 -0
  54. package/builtin-skills/todo-delete/main.py +65 -0
  55. package/builtin-skills/todo-delete/skill.yaml +47 -0
  56. package/builtin-skills/todo-done/main.py +75 -0
  57. package/builtin-skills/todo-done/skill.yaml +47 -0
  58. package/builtin-skills/todo-list/main.py +91 -0
  59. package/builtin-skills/todo-list/skill.yaml +48 -0
  60. package/builtin-skills/todo-tick/main.py +125 -0
  61. package/builtin-skills/todo-tick/skill.yaml +48 -0
  62. package/dist/builder/build-action-classifier.d.ts +18 -0
  63. package/dist/builder/build-action-classifier.js +142 -0
  64. package/dist/builder/build-commands.d.ts +19 -0
  65. package/dist/builder/build-commands.js +133 -0
  66. package/dist/builder/build-flow.d.ts +72 -0
  67. package/dist/builder/build-flow.js +416 -0
  68. package/dist/builder/builder-session.d.ts +117 -0
  69. package/dist/builder/builder-session.js +1129 -0
  70. package/dist/builder/conversation-router.d.ts +22 -0
  71. package/dist/builder/conversation-router.js +69 -0
  72. package/dist/builder/intent-runtime-store.d.ts +7 -0
  73. package/dist/builder/intent-runtime-store.js +60 -0
  74. package/dist/builder/progress-format.d.ts +7 -0
  75. package/dist/builder/progress-format.js +46 -0
  76. package/dist/cli/index.d.ts +2 -0
  77. package/dist/cli/index.js +2835 -0
  78. package/dist/cli/spinner.d.ts +30 -0
  79. package/dist/cli/spinner.js +164 -0
  80. package/dist/core/ai-provider.d.ts +75 -0
  81. package/dist/core/ai-provider.js +678 -0
  82. package/dist/core/builtin-skills.d.ts +27 -0
  83. package/dist/core/builtin-skills.js +120 -0
  84. package/dist/core/chat-engine.d.ts +70 -0
  85. package/dist/core/chat-engine.js +812 -0
  86. package/dist/core/config.d.ts +127 -0
  87. package/dist/core/config.js +1379 -0
  88. package/dist/core/daily-memory-promotion.d.ts +21 -0
  89. package/dist/core/daily-memory-promotion.js +422 -0
  90. package/dist/core/i18n.d.ts +23 -0
  91. package/dist/core/i18n.js +167 -0
  92. package/dist/core/info-lines.d.ts +5 -0
  93. package/dist/core/info-lines.js +39 -0
  94. package/dist/core/input-schema.d.ts +2 -0
  95. package/dist/core/input-schema.js +156 -0
  96. package/dist/core/intent-router.d.ts +27 -0
  97. package/dist/core/intent-router.js +160 -0
  98. package/dist/core/logger.d.ts +60 -0
  99. package/dist/core/logger.js +240 -0
  100. package/dist/core/memory.d.ts +10 -0
  101. package/dist/core/memory.js +72 -0
  102. package/dist/core/message-utils.d.ts +13 -0
  103. package/dist/core/message-utils.js +104 -0
  104. package/dist/core/notifier.d.ts +17 -0
  105. package/dist/core/notifier.js +424 -0
  106. package/dist/core/output-writer.d.ts +13 -0
  107. package/dist/core/output-writer.js +100 -0
  108. package/dist/core/plan-decision.d.ts +16 -0
  109. package/dist/core/plan-decision.js +88 -0
  110. package/dist/core/profile-memory.d.ts +17 -0
  111. package/dist/core/profile-memory.js +142 -0
  112. package/dist/core/runtime.d.ts +50 -0
  113. package/dist/core/runtime.js +187 -0
  114. package/dist/core/scheduler.d.ts +28 -0
  115. package/dist/core/scheduler.js +155 -0
  116. package/dist/core/secrets.d.ts +31 -0
  117. package/dist/core/secrets.js +214 -0
  118. package/dist/core/service.d.ts +35 -0
  119. package/dist/core/service.js +479 -0
  120. package/dist/core/skill-planner.d.ts +24 -0
  121. package/dist/core/skill-planner.js +100 -0
  122. package/dist/core/skill-registry.d.ts +98 -0
  123. package/dist/core/skill-registry.js +319 -0
  124. package/dist/core/skill-scaffold.d.ts +33 -0
  125. package/dist/core/skill-scaffold.js +256 -0
  126. package/dist/core/skill-settings.d.ts +11 -0
  127. package/dist/core/skill-settings.js +63 -0
  128. package/dist/core/transfer.d.ts +31 -0
  129. package/dist/core/transfer.js +270 -0
  130. package/dist/core/update-notifier.d.ts +2 -0
  131. package/dist/core/update-notifier.js +140 -0
  132. package/dist/discord/bot.d.ts +96 -0
  133. package/dist/discord/bot.js +2424 -0
  134. package/dist/runtimes/codex-app-server.d.ts +53 -0
  135. package/dist/runtimes/codex-app-server.js +305 -0
  136. package/dist/runtimes/python-runner.d.ts +7 -0
  137. package/dist/runtimes/python-runner.js +302 -0
  138. package/dist/runtimes/typescript-runner.d.ts +5 -0
  139. package/dist/runtimes/typescript-runner.js +172 -0
  140. package/dist/skills-sdk/types.d.ts +38 -0
  141. package/dist/skills-sdk/types.js +1 -0
  142. package/dist/telegram/bot.d.ts +94 -0
  143. package/dist/telegram/bot.js +1219 -0
  144. package/install.ps1 +132 -0
  145. package/install.sh +130 -0
  146. package/package.json +60 -0
  147. package/templates/skill-python/main.py +74 -0
  148. package/templates/skill-python/skill.yaml +48 -0
  149. package/templates/skill-typescript/main.ts +87 -0
  150. package/templates/skill-typescript/skill.yaml +42 -0
@@ -0,0 +1,68 @@
1
+ """Builtin: todo-add
2
+
3
+ ToDo を1件追加する。memory.todo namespace の "items" リストに append する。
4
+
5
+ 入力:
6
+ args.text: ToDoの本文 (必須)
7
+ args.due: ISO8601 日時 (任意)
8
+ 出力:
9
+ data.item: 追加したToDo
10
+ data.total: 追加後の総件数
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import secrets
16
+ import os
17
+ import sys
18
+ from datetime import datetime
19
+
20
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
21
+ from i18n import localizer # noqa: E402
22
+
23
+
24
+ async def run(ctx, input):
25
+ loc = localizer(input)
26
+ args = input.get("args", {})
27
+ text = str(args.get("text", "")).strip()
28
+ due = str(args.get("due", "")).strip() or None
29
+
30
+ if not text:
31
+ return result_skipped(loc.t("No ToDo", "ToDoなし"), loc.t("There is nothing to add.", "追加する内容がありません"))
32
+
33
+ items = list((await ctx.memory.get("items")) or [])
34
+ now = input.get("trigger", {}).get("time") or datetime.now().isoformat()
35
+ item = {
36
+ "id": secrets.token_hex(3),
37
+ "text": text,
38
+ "status": "open",
39
+ "created_at": now,
40
+ }
41
+ if due:
42
+ item["due"] = due
43
+ items.append(item)
44
+ await ctx.memory.set("items", items)
45
+ ctx.log.info(f"todo-add: id={item['id']} total={len(items)}")
46
+
47
+ summary = loc.t(f"Added: {text}", f"追加: {text}")
48
+ if due:
49
+ summary += loc.t(f" (due {due})", f" (期限 {due})")
50
+ return {
51
+ "status": "ok",
52
+ "title": loc.t("Added", "追加"),
53
+ "summary": summary,
54
+ "outputs": {},
55
+ "data": {"item": item, "total": len(items)},
56
+ "suggestions": [],
57
+ }
58
+
59
+
60
+ def result_skipped(title, summary):
61
+ return {
62
+ "status": "skipped",
63
+ "title": title,
64
+ "summary": summary,
65
+ "outputs": {},
66
+ "data": {},
67
+ "suggestions": [],
68
+ }
@@ -0,0 +1,53 @@
1
+ # Builtin: todo-add
2
+ # ToDo を1件追加する。状態は memory.todo namespace に保存される。
3
+
4
+ id: todo-add
5
+ name: ToDo Add
6
+ name_i18n:
7
+ en: ToDo Add
8
+ ja: ToDo追加
9
+ description: ToDoを1件追加する
10
+ description_i18n:
11
+ en: Add one ToDo item
12
+ ja: ToDoを1件追加する
13
+ runtime: python
14
+ output_mode: raw # スキル結果をそのままユーザーに返し、LLM 再整形ターンを挟まない
15
+ side_effect: true
16
+
17
+ invocation:
18
+ command: todo.add
19
+ phrases:
20
+ - todo追加
21
+ - やることを追加
22
+ - タスク追加
23
+ phrases_i18n:
24
+ en:
25
+ - add todo
26
+ - add a task
27
+ - create todo
28
+ ja:
29
+ - todo追加
30
+ - やることを追加
31
+ - タスク追加
32
+
33
+ input:
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ properties:
38
+ text:
39
+ type: string
40
+ minLength: 1
41
+ due:
42
+ type: string
43
+ description: ISO8601 日時 (任意, 例 2026-05-17T18:00:00+09:00)。日付だけでなく時刻まで指定でき、todo-tick が到達時にctx.notifyで通知する
44
+ description_i18n:
45
+ en: Optional ISO8601 date-time, e.g. 2026-05-17T18:00:00+09:00. Include a time so todo-tick can notify when it arrives
46
+ ja: ISO8601 日時 (任意, 例 2026-05-17T18:00:00+09:00)。日付だけでなく時刻まで指定でき、todo-tick が到達時にctx.notifyで通知する
47
+ required:
48
+ - text
49
+
50
+ memory:
51
+ namespace: todo
52
+ read: true
53
+ write: true
@@ -0,0 +1,65 @@
1
+ """Builtin: todo-delete
2
+
3
+ ToDoを削除する。argsで渡されたidの完全一致または先頭一致で1件特定する。
4
+
5
+ 入力:
6
+ args.id: 対象ToDoのID (先頭一致可)
7
+ 出力:
8
+ data.removed: 削除したToDo
9
+ data.total: 削除後の総件数
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import sys
16
+
17
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
18
+ from i18n import localizer # noqa: E402
19
+
20
+
21
+ async def run(ctx, input):
22
+ loc = localizer(input)
23
+ args = input.get("args", {})
24
+ target_id = str(args.get("id", "")).strip()
25
+
26
+ if not target_id:
27
+ return error_result(loc.t("ID is missing", "IDが指定されていません"), loc.t("Specify the ToDo ID to delete.", "削除するToDoのIDを指定してください"))
28
+
29
+ items = list((await ctx.memory.get("items")) or [])
30
+ if not items:
31
+ return error_result(loc.t("No ToDos", "ToDoがありません"), loc.t("There are no registered ToDos yet.", "ToDoがまだ登録されていません"))
32
+
33
+ matches = [i for i in items if i.get("id") == target_id]
34
+ if not matches:
35
+ matches = [i for i in items if str(i.get("id", "")).startswith(target_id)]
36
+ if not matches:
37
+ return error_result(loc.t("Not found", "見つかりません"), loc.t(f"No ToDo was found for ID {target_id}.", f"ID {target_id} のToDoは見つかりませんでした"))
38
+ if len(matches) > 1:
39
+ ids = ", ".join(str(i.get("id")) for i in matches)
40
+ return error_result(loc.t("Ambiguous ID", "IDが曖昧です"), loc.t(f"Multiple ToDos match: {ids}", f"複数のToDoが該当します: {ids}"))
41
+
42
+ target = matches[0]
43
+ remaining = [i for i in items if i is not target]
44
+ await ctx.memory.set("items", remaining)
45
+ ctx.log.info(f"todo-delete: id={target.get('id')} remaining={len(remaining)}")
46
+
47
+ return {
48
+ "status": "ok",
49
+ "title": loc.t("Deleted", "削除"),
50
+ "summary": loc.t(f"Deleted: {target.get('text', '')}", f"削除: {target.get('text', '')}"),
51
+ "outputs": {},
52
+ "data": {"removed": target, "total": len(remaining)},
53
+ "suggestions": [],
54
+ }
55
+
56
+
57
+ def error_result(title, summary):
58
+ return {
59
+ "status": "error",
60
+ "title": title,
61
+ "summary": summary,
62
+ "outputs": {},
63
+ "data": {},
64
+ "suggestions": [],
65
+ }
@@ -0,0 +1,47 @@
1
+ # Builtin: todo-delete
2
+ # 指定したToDoを削除する。
3
+
4
+ id: todo-delete
5
+ name: ToDo Delete
6
+ name_i18n:
7
+ en: ToDo Delete
8
+ ja: ToDo削除
9
+ description: ToDoを削除する
10
+ description_i18n:
11
+ en: Delete a ToDo item
12
+ ja: ToDoを削除する
13
+ runtime: python
14
+ output_mode: raw # スキル結果をそのままユーザーに返し、LLM 再整形ターンを挟まない
15
+ side_effect: true
16
+
17
+ invocation:
18
+ command: todo.delete
19
+ phrases:
20
+ - todo削除
21
+ - 削除
22
+ - 消して
23
+ phrases_i18n:
24
+ en:
25
+ - delete todo
26
+ - remove task
27
+ - delete task
28
+ ja:
29
+ - todo削除
30
+ - 削除
31
+ - 消して
32
+
33
+ input:
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ properties:
38
+ id:
39
+ type: string
40
+ minLength: 1
41
+ required:
42
+ - id
43
+
44
+ memory:
45
+ namespace: todo
46
+ read: true
47
+ write: true
@@ -0,0 +1,75 @@
1
+ """Builtin: todo-done
2
+
3
+ ToDoを完了状態に更新する。argsで渡されたidの完全一致または先頭一致で1件特定する。
4
+
5
+ 入力:
6
+ args.id: 対象ToDoのID (先頭一致可)
7
+ 出力:
8
+ data.item: 更新後のToDo
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ from datetime import datetime
16
+
17
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
18
+ from i18n import localizer # noqa: E402
19
+
20
+
21
+ async def run(ctx, input):
22
+ loc = localizer(input)
23
+ args = input.get("args", {})
24
+ target_id = str(args.get("id", "")).strip()
25
+
26
+ if not target_id:
27
+ return error_result(loc.t("ID is missing", "IDが指定されていません"), loc.t("Specify the ToDo ID to complete.", "完了するToDoのIDを指定してください"))
28
+
29
+ items = list((await ctx.memory.get("items")) or [])
30
+ if not items:
31
+ return error_result(loc.t("No ToDos", "ToDoがありません"), loc.t("There are no registered ToDos yet.", "ToDoがまだ登録されていません"))
32
+
33
+ matches = [i for i in items if i.get("id") == target_id]
34
+ if not matches:
35
+ matches = [i for i in items if str(i.get("id", "")).startswith(target_id)]
36
+ if not matches:
37
+ return error_result(loc.t("Not found", "見つかりません"), loc.t(f"No ToDo was found for ID {target_id}.", f"ID {target_id} のToDoは見つかりませんでした"))
38
+ if len(matches) > 1:
39
+ ids = ", ".join(str(i.get("id")) for i in matches)
40
+ return error_result(loc.t("Ambiguous ID", "IDが曖昧です"), loc.t(f"Multiple ToDos match: {ids}", f"複数のToDoが該当します: {ids}"))
41
+
42
+ target = matches[0]
43
+ text = target.get("text", "")
44
+ if target.get("status") == "done":
45
+ return result_ok(loc.t("Already done", "既に完了済み"), loc.t(f"Already done: {text}", f"既に完了済み: {text}"), target)
46
+
47
+ now = input.get("trigger", {}).get("time") or datetime.now().isoformat()
48
+ target["status"] = "done"
49
+ target["completed_at"] = now
50
+ await ctx.memory.set("items", items)
51
+ ctx.log.info(f"todo-done: id={target.get('id')}")
52
+
53
+ return result_ok(loc.t("Done", "完了"), loc.t(f"Done: {text}", f"完了: {text}"), target)
54
+
55
+
56
+ def result_ok(title, summary, item):
57
+ return {
58
+ "status": "ok",
59
+ "title": title,
60
+ "summary": summary,
61
+ "outputs": {},
62
+ "data": {"item": item},
63
+ "suggestions": [],
64
+ }
65
+
66
+
67
+ def error_result(title, summary):
68
+ return {
69
+ "status": "error",
70
+ "title": title,
71
+ "summary": summary,
72
+ "outputs": {},
73
+ "data": {},
74
+ "suggestions": [],
75
+ }
@@ -0,0 +1,47 @@
1
+ # Builtin: todo-done
2
+ # 指定したToDoを完了状態に更新する。idの先頭一致でも対応。
3
+
4
+ id: todo-done
5
+ name: ToDo Done
6
+ name_i18n:
7
+ en: ToDo Done
8
+ ja: ToDo完了
9
+ description: ToDoを完了済みにする
10
+ description_i18n:
11
+ en: Mark a ToDo item as done
12
+ ja: ToDoを完了済みにする
13
+ runtime: python
14
+ output_mode: raw # スキル結果をそのままユーザーに返し、LLM 再整形ターンを挟まない
15
+ side_effect: true
16
+
17
+ invocation:
18
+ command: todo.done
19
+ phrases:
20
+ - todo完了
21
+ - 完了
22
+ - チェック
23
+ phrases_i18n:
24
+ en:
25
+ - complete todo
26
+ - mark todo done
27
+ - check off task
28
+ ja:
29
+ - todo完了
30
+ - 完了
31
+ - チェック
32
+
33
+ input:
34
+ schema:
35
+ type: object
36
+ additionalProperties: false
37
+ properties:
38
+ id:
39
+ type: string
40
+ minLength: 1
41
+ required:
42
+ - id
43
+
44
+ memory:
45
+ namespace: todo
46
+ read: true
47
+ write: true
@@ -0,0 +1,91 @@
1
+ """Builtin: todo-list
2
+
3
+ memory.todo namespace の ToDo を一覧表示する。
4
+ status=open(default) / done / all で絞り込める。
5
+
6
+ 入力:
7
+ args.status: open | done | all (default: open)
8
+ 出力:
9
+ data.items: 該当ToDoの配列
10
+ data.counts: { open, done, total }
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import sys
17
+ from datetime import datetime, timezone
18
+
19
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
20
+ from i18n import localizer # noqa: E402
21
+
22
+
23
+ async def run(ctx, input):
24
+ loc = localizer(input)
25
+ args = input.get("args", {})
26
+ status = str(args.get("status", "open")).strip() or "open"
27
+
28
+ items = list((await ctx.memory.get("items")) or [])
29
+ open_items = [i for i in items if i.get("status") == "open"]
30
+ done_items = [i for i in items if i.get("status") == "done"]
31
+ if status == "open":
32
+ shown = open_items
33
+ elif status == "done":
34
+ shown = done_items
35
+ else:
36
+ shown = items
37
+
38
+ counts = {"open": len(open_items), "done": len(done_items), "total": len(items)}
39
+
40
+ if not shown:
41
+ if status == "open":
42
+ return result_ok(loc.t("No ToDos", "ToDoなし"), loc.t("There are no open ToDos.", "未完了のToDoはありません"), shown, counts)
43
+ if status == "done":
44
+ return result_ok(loc.t("No ToDos", "ToDoなし"), loc.t("There are no completed ToDos.", "完了済みのToDoはありません"), shown, counts)
45
+ return result_ok(loc.t("No ToDos", "ToDoなし"), loc.t("There are no ToDos yet.", "ToDoがまだありません"), shown, counts)
46
+
47
+ now = parse_iso(input.get("trigger", {}).get("time")) or datetime.now(timezone.utc)
48
+
49
+ lines = []
50
+ for item in shown:
51
+ mark = "x" if loc.locale != "ja" and item.get("status") == "done" else "✔" if item.get("status") == "done" else "・"
52
+ line = f"{mark} {item.get('text', '')}"
53
+ due = item.get("due")
54
+ if due:
55
+ due_dt = parse_iso(due)
56
+ suffix = ""
57
+ if item.get("status") == "open" and due_dt and due_dt <= now:
58
+ suffix = loc.t(" overdue", " ⚠期限切れ")
59
+ elif item.get("notified_at"):
60
+ suffix = loc.t(" notified", " 🔔通知済")
61
+ line += loc.t(f" (due {due}){suffix}", f" (期限 {due}){suffix}")
62
+ item_id = item.get("id")
63
+ if item_id:
64
+ line += f" · {item_id}"
65
+ lines.append(line)
66
+
67
+ title = loc.t(f"{len(shown)} ToDos", f"ToDo {len(shown)}件")
68
+ return result_ok(title, "\n".join(lines), shown, counts)
69
+
70
+
71
+ def parse_iso(value):
72
+ if not value:
73
+ return None
74
+ try:
75
+ parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
76
+ except ValueError:
77
+ return None
78
+ if parsed.tzinfo is None:
79
+ parsed = parsed.replace(tzinfo=timezone.utc)
80
+ return parsed
81
+
82
+
83
+ def result_ok(title, summary, items, counts):
84
+ return {
85
+ "status": "ok",
86
+ "title": title,
87
+ "summary": summary,
88
+ "outputs": {},
89
+ "data": {"items": items, "counts": counts},
90
+ "suggestions": [],
91
+ }
@@ -0,0 +1,48 @@
1
+ # Builtin: todo-list
2
+ # memory.todo namespace に保存された ToDo を一覧表示する読み取り専用スキル。
3
+
4
+ id: todo-list
5
+ name: ToDo List
6
+ name_i18n:
7
+ en: ToDo List
8
+ ja: ToDo一覧
9
+ description: ToDoの一覧を表示する
10
+ description_i18n:
11
+ en: List ToDo items
12
+ ja: ToDoの一覧を表示する
13
+ runtime: python
14
+ output_mode: raw # スキル結果をそのままユーザーに返し、LLM 再整形ターンを挟まない
15
+
16
+ invocation:
17
+ command: todo.list
18
+ phrases:
19
+ - todo一覧
20
+ - todo
21
+ - やること
22
+ - タスク一覧
23
+ phrases_i18n:
24
+ en:
25
+ - list todos
26
+ - show todos
27
+ - task list
28
+ ja:
29
+ - todo一覧
30
+ - todo
31
+ - やること
32
+ - タスク一覧
33
+
34
+ input:
35
+ schema:
36
+ type: object
37
+ additionalProperties: false
38
+ properties:
39
+ status:
40
+ type: string
41
+ enum: [open, done, all]
42
+ default: open
43
+ required: []
44
+
45
+ memory:
46
+ namespace: todo
47
+ read: true
48
+ write: false
@@ -0,0 +1,125 @@
1
+ """Builtin: todo-tick
2
+
3
+ memory.todo namespace の open ToDo を走査し、due が現在時刻に達したものを
4
+ ctx.notify で通知する。重複通知を避けるため notified_at を打つ。
5
+ 同じ期限時刻(分単位)のToDoは1通にまとめて通知する。
6
+
7
+ 入力:
8
+ args.channel: 通知チャネル (auto/macos/discord/telegram/slack/mail/stderr, default: auto)
9
+ 出力:
10
+ data.fired: 通知したToDoの配列
11
+ data.pending: 未通知のopen ToDo件数(due未設定 or 未到達も含む)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import sys
18
+ from collections import OrderedDict
19
+ from datetime import datetime, timezone
20
+
21
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
22
+ from i18n import localizer # noqa: E402
23
+
24
+
25
+ async def run(ctx, input):
26
+ loc = localizer(input)
27
+ args = input.get("args", {})
28
+ channel = str(args.get("channel", "auto")).strip() or "auto"
29
+
30
+ items = list((await ctx.memory.get("items")) or [])
31
+ if not items:
32
+ return result_ok(loc.t("Nothing due", "対象なし"), loc.t("There are no ToDos.", "ToDoはありません"), [], 0)
33
+
34
+ now_str = input.get("trigger", {}).get("time")
35
+ now = parse_iso(now_str) or datetime.now(timezone.utc)
36
+
37
+ # 同じ期限(分単位)のToDoをまとめて1通にする
38
+ groups: "OrderedDict[str, list]" = OrderedDict()
39
+ for item in items:
40
+ if item.get("status") != "open":
41
+ continue
42
+ if item.get("notified_at"):
43
+ continue
44
+ due = parse_iso(item.get("due"))
45
+ if not due or due > now:
46
+ continue
47
+ key = due.replace(second=0, microsecond=0).isoformat()
48
+ groups.setdefault(key, []).append(item)
49
+
50
+ fired = []
51
+ failed = []
52
+ for group in groups.values():
53
+ body = "\n".join(i.get("text", "ToDo") for i in group)
54
+ outcome = await ctx.notify({
55
+ "title": loc.t("ToDo due", "ToDoの期限"),
56
+ "body": body,
57
+ "channel": channel,
58
+ })
59
+ if outcome.get("ok"):
60
+ stamp = now.isoformat()
61
+ for item in group:
62
+ item["notified_at"] = stamp
63
+ fired.append(item)
64
+ ctx.log.info(f"todo-tick: fired id={item.get('id')}")
65
+ else:
66
+ detail = outcome.get("detail") or "unknown"
67
+ for item in group:
68
+ failed.append({"id": item.get("id"), "error": detail})
69
+ ctx.log.warn(f"todo-tick: notify failed id={item.get('id')} - {detail}")
70
+
71
+ if fired:
72
+ await ctx.memory.set("items", items)
73
+
74
+ pending = sum(
75
+ 1 for i in items
76
+ if i.get("status") == "open" and not i.get("notified_at")
77
+ )
78
+
79
+ if not fired and not failed:
80
+ return result_ok(loc.t("No notifications", "通知なし"), loc.t("No ToDos are due.", "期限到達のToDoはありません"), [], pending)
81
+ if failed and not fired:
82
+ return {
83
+ "status": "error",
84
+ "title": loc.t("Notification failed", "通知に失敗しました"),
85
+ "summary": loc.t(f"{len(failed)} notifications failed.", f"{len(failed)}件の通知に失敗しました"),
86
+ "outputs": {},
87
+ "data": {"fired": [], "failed": failed, "pending": pending},
88
+ "suggestions": [],
89
+ }
90
+
91
+ title = loc.t(f"Sent {len(fired)} notifications", f"{len(fired)}件を通知しました")
92
+ lines = [f"- {i.get('text', '')}" for i in fired]
93
+ if failed:
94
+ lines.append(loc.t(f"({len(failed)} failed)", f"(失敗 {len(failed)}件)"))
95
+ return {
96
+ "status": "ok",
97
+ "title": title,
98
+ "summary": "\n".join(lines),
99
+ "outputs": {},
100
+ "data": {"fired": fired, "failed": failed, "pending": pending},
101
+ "suggestions": [],
102
+ }
103
+
104
+
105
+ def parse_iso(value):
106
+ if not value:
107
+ return None
108
+ try:
109
+ parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
110
+ except ValueError:
111
+ return None
112
+ if parsed.tzinfo is None:
113
+ parsed = parsed.replace(tzinfo=timezone.utc)
114
+ return parsed
115
+
116
+
117
+ def result_ok(title, summary, fired, pending):
118
+ return {
119
+ "status": "ok",
120
+ "title": title,
121
+ "summary": summary,
122
+ "outputs": {},
123
+ "data": {"fired": fired, "pending": pending},
124
+ "suggestions": [],
125
+ }
@@ -0,0 +1,48 @@
1
+ # Builtin: todo-tick
2
+ # 期限を過ぎた open ToDo を通知して notified_at を打つ。
3
+ # schedules.yaml に毎分実行で登録するか、手動で `agent-sin run todo-tick` を呼ぶ。
4
+
5
+ id: todo-tick
6
+ name: ToDo Tick
7
+ name_i18n:
8
+ en: ToDo Tick
9
+ ja: ToDo期限通知
10
+ description: 期限が到達したToDoを通知する
11
+ description_i18n:
12
+ en: Notify when ToDo due times are reached
13
+ ja: 期限が到達したToDoを通知する
14
+ runtime: python
15
+ output_mode: raw
16
+ side_effect: true
17
+
18
+ invocation:
19
+ command: todo.tick
20
+ phrases:
21
+ - 期限のToDoを確認
22
+ - ToDo通知をチェック
23
+ - todoの期限チェック
24
+ phrases_i18n:
25
+ en:
26
+ - check due todos
27
+ - todo notifications
28
+ - check todo deadlines
29
+ ja:
30
+ - 期限のToDoを確認
31
+ - ToDo通知をチェック
32
+ - todoの期限チェック
33
+
34
+ input:
35
+ schema:
36
+ type: object
37
+ additionalProperties: false
38
+ properties:
39
+ channel:
40
+ type: string
41
+ enum: [auto, macos, discord, telegram, slack, mail, stderr]
42
+ default: auto
43
+ required: []
44
+
45
+ memory:
46
+ namespace: todo
47
+ read: true
48
+ write: true
@@ -0,0 +1,18 @@
1
+ import type { AppConfig } from "../core/config.js";
2
+ import type { ChatTurn } from "../core/chat-engine.js";
3
+ import type { BuildModeState, PendingHandoff } from "./build-flow.js";
4
+ export interface HandoffApprovalDecision {
5
+ decision: "approve" | "reject" | "discuss";
6
+ carry_over_text?: string;
7
+ reason?: string;
8
+ }
9
+ export interface BuildModeActionDecision {
10
+ action: "exit" | "register" | "test" | "continue";
11
+ reason?: string;
12
+ }
13
+ interface ClassifyOptions {
14
+ modelId?: string;
15
+ }
16
+ export declare function classifyHandoffApproval(config: AppConfig, userText: string, history: ChatTurn[], pending: PendingHandoff, options?: ClassifyOptions): Promise<HandoffApprovalDecision>;
17
+ export declare function classifyBuildModeAction(config: AppConfig, userText: string, history: ChatTurn[], build: BuildModeState, options?: ClassifyOptions): Promise<BuildModeActionDecision>;
18
+ export {};